﻿using System;
using System.Diagnostics;

namespace CatUtils.Utils.SpecialUtils.WinConsole
{
    internal class ProcessHelper
    {/// <summary>
     /// 获取本地CMD路径
     /// </summary>
        public readonly static string SystemCmdPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.System)}\\cmd.exe";

        /// <summary>
        /// 创建Process基础配置方法
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        /// <param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <returns></returns>
        private Process CreateProcess(string ProcessFilePath, string Cmd, string Arguments, bool AutoExit)
        {
            if (AutoExit)
            {
                Cmd = Cmd.Trim().TrimEnd('&') + " &exit";//说明：不管命令是否成功均执行exit命令，否则当调用ReadToEnd()方法时，会处于假死状态
            }
            Process p = new Process();
            p.StartInfo.FileName = ProcessFilePath;
            p.StartInfo.UseShellExecute = false;        //是否使用操作系统shell启动
            p.StartInfo.RedirectStandardInput = true;   //接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardOutput = true;  //由调用程序获取输出信息
            p.StartInfo.RedirectStandardError = true;   //重定向标准错误输出
            p.StartInfo.Arguments = Arguments;//设定要执行的命令
            p.Start();

            p.StandardInput.WriteLine(Cmd);

            //获取cmd窗口的输出信息
            p.StandardInput.AutoFlush = true;

            return p;
        }

        /// <summary>
        ///  /// 执行cmd命令
        /// 多命令请使用批处理命令连接符：
        /// <![CDATA[
        /// &:同时执行两个命令
        /// |:将上一个命令的输出,作为下一个命令的输入
        /// &&：当&&前的命令成功时,才执行&&后的命令
        /// ||：当||前的命令失败时,才执行||后的命令]]>
        /// 其他请百度
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <returns></returns>
        public string RunCmdReadStandardOutput(string ProcessFilePath, string Cmd, string Arguments = "", bool AutoExit = false)
        {
            string Relust = "";

            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                Relust = p.StandardOutput.ReadToEnd();
                p.WaitForExit();//等待程序执行完退出进程
            }

            return Relust;
        }

        /// <summary>
        /// 读取错误日志并且关闭，专为FFMPEG使用，不然程序会假死
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <returns></returns>
        public string RunCmdReadStandardError(string ProcessFilePath, string Cmd, string Arguments = "", bool AutoExit = false)
        {
            string Relust = "";
            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                //获取cmd窗口的输出信息
                Relust = p.StandardError.ReadToEnd();
                p.WaitForExit();//等待程序执行完退出进程
            }

            return Relust;
        }

        /// <summary>
        /// 读取所有数据，包括错误日志 但有些程序会因为输出错误日志失败而导致程序卡死
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <returns></returns>
        public string RunCmdReadAll(string ProcessFilePath, string Cmd, string Arguments = "", bool AutoExit = false)
        {
            string Relust = "";
            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                //获取cmd窗口的输出信息
                Relust = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                p.WaitForExit();//等待程序执行完退出进程
            }

            return Relust;
        }

        /// <summary>
        /// 异步执行命令行
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <param name="BeginErrorReadLine"></param>
        public void RunCmdAsyncBeginError(string ProcessFilePath, string Cmd, DataReceivedEventHandler BeginErrorReadLine, string Arguments = "", bool AutoExit = false)
        { //   Cmd = Cmd.Trim().TrimEnd('&') + " &exit";//说明：不管命令是否成功均执行exit命令，否则当调用ReadToEnd()方法时，会处于假死状态
            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                p.ErrorDataReceived += new DataReceivedEventHandler(BeginErrorReadLine);

                p.BeginOutputReadLine();//开始异步读取
                p.BeginErrorReadLine();//开始异步读取

                p.WaitForExit();//等待程序执行完退出进程
            }
        }

        /// <summary>
        /// 异步执行命令行
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        /// <param name="receivedEventHandler"></param>
        public void RunCmdAsyncReceived(string ProcessFilePath, string Cmd, DataReceivedEventHandler receivedEventHandler, string Arguments = "", bool AutoExit = false)
        { //   Cmd = Cmd.Trim().TrimEnd('&') + " &exit";//说明：不管命令是否成功均执行exit命令，否则当调用ReadToEnd()方法时，会处于假死状态
            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                p.OutputDataReceived += new DataReceivedEventHandler(receivedEventHandler);

                p.BeginOutputReadLine();//开始异步读取
                p.BeginErrorReadLine();//开始异步读取

                p.WaitForExit();//等待程序执行完退出进程
            }
        }

        /// <summary>
        /// 异步执行命令行
        /// </summary>
        /// <param name="ProcessFilePath"></param>
        /// <param name="Cmd">要使用的CMD命令，如果程序假死可尝试启用autoexit，或者自行在命令尾部加入 &exit</param>
        ///<param name="Arguments">程序启动时的预设参数 并不等同于CMD命令。如果启动程序时同步输入参数请用这个,如果用Cmd输入命令则用Cmd参数</param>
        /// <param name="receivedEventHandler"></param>
        /// <param name="BeginErrorReadLine"></param>
        /// <param name="AutoExit">是否自动退出,若是程序陷入假死状态，请尝试开启此配置。</param>
        public void RunCmdAsyncAll(string ProcessFilePath, string Cmd,
            DataReceivedEventHandler receivedEventHandler, DataReceivedEventHandler BeginErrorReadLine, string Arguments = "", bool AutoExit = false)
        { //   Cmd = Cmd.Trim().TrimEnd('&') + " &exit";//说明：不管命令是否成功均执行exit命令，否则当调用ReadToEnd()方法时，会处于假死状态
            using (Process p = CreateProcess(ProcessFilePath, Cmd, Arguments, AutoExit))
            {
                p.ErrorDataReceived += new DataReceivedEventHandler(receivedEventHandler);
                p.OutputDataReceived += new DataReceivedEventHandler(BeginErrorReadLine);

                p.BeginOutputReadLine();//开始异步读取
                p.BeginErrorReadLine();//开始异步读取

                p.WaitForExit();//等待程序执行完退出进程
            }
        }
    }
}